package NetWork;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket;  //ServerSocket相当于服务器专属的socket 相当于打电话时拨号
    public TcpEchoServer(int port) throws  IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器!!!");
        //这里的accept的效果是建立链接, 前提是 : 得有客户端来建立链接  如果没有客服端来连接,就会阻塞等待
        //客户端在构造socket对象的时候,就会指定服务器的ip和端口
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while (true) {
            /*使用这个 clientSocket 和具体的客户端进行交流
              通过serverSocket拨号成功后,会建立一个连接,此时就相当于两个手机进行通信 ,此时客户端传入数据,那么这个socket对象也就能接收数据
              问题一:任意一个客服端连上来,都会返回/创建一个Socket对象,(Socket就是文件)每一次创建一个clientSocket对象都会占用一个文件描述符表
              因此在使用完毕之后需要释放,前面的socket都没有释放,一方面这些socket生命周期更长(跟随整个程序),另一方面这些socket也不多,固定数量
              但是此处的clientSocket数量多,每一个客户端上都有一个,如果不释放很快就会吧文件描述符表占满,之后再打开文件就打开不了
              问题二:只能和一个客户端进行通信,此时另外的客户端要和服务器通信需要先建立链接,
              而此时的代码逻辑:因为是processConnection里面是while(true)所以会一直处理请求,直到客户端输入exit才会结束这个processConnection
              才能让别的客户端连接服务器,而如果不exit那么此时就会一直执行processConnection(clientSocket)这段代码,此时别的客户端就连接不了
              处理方法 : 使用多线程(如果客户端特别多,那么就会频繁创建销毁线程,此时使用线程池)
             */
            Socket clientSocket = serverSocket.accept();
            /*多线程版本,最大的问题就是会频繁创建销毁线程*/
//            Thread t = new Thread(() -> {
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            });
            //此处不适用newFixedThreadPool 是因为当客户端数量很多时,需要创建的线程数是不确定的
            //问题三:如果有很多客户端,那么就会创建很多个线程,而一台服务器承担不起这样的线程开销 解决办法:一个线程处理多个客户端(IO多路 复用/转接)
            //给这个线程安排一个集合,这个集合放了一堆连接,这个线程负责监听这个集合,那个连接有数据来了,线程就处理那个连接(虽然这些连接有很多但是并不可能是一起执行的)
            //在操作系统中,提供了一些原生的api select,poll,epoll
            threadPool.submit(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }

    }

    //使用这个方法来处理一个连接
    //这一个连接对应一个客户端, 但是这里可能会涉及到多次交互
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("%s:%d 客服端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //基于上述socket对象和客户端进行通信
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            //因为要处理多个请求和响应,所以使用循环来进行
            while (true) {
                //1.读取请求
                Scanner resScanner = new Scanner(inputStream);
                if(!resScanner.hasNext()) {
                    System.out.printf("%s:%d 客服端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //注意 此处使用的next是一直读取到换行符/空格/其他空白符结束,但是最终返回结果里不包含上述空白符
                String request = resScanner.next();
                //2.根据请求构造响应
                String response = process(request);
                //3.返回响应结果
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
                //flush用来刷新缓冲区,保证数据确实发出去了
                System.out.printf("%s:%d req : %s reps : %s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}